package com.wiiss.ams.util;

import cn.hutool.core.date.LocalDateTimeUtil;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * id 生成等相关工具 鉴于javascript Number类型的安全值为2^53-1 设计如下id生成方案:<br>
 * 前33位放时间戳(单位秒)<br>
 * 后20位放秒内流水号 最大值1048575 每超一次1048575 秒时间戳值+1 优点: <br>
 * 递增 javascript Number类型安全 和数据库的bigint类型/前端 互相直接传递 不用转String类型 <br>
 * 生成器可传表名为参数 即支持为指定表生成id 多表互不影响(多数情况下 不同表是可以同id的) <br>
 * 可以通过id解析出时间戳 在保证秒级精确下 单表最大tps 1048576<br>
 * 可以用到2242年<br>
 * 缺点:<br>
 * 在单表每毫秒产生超1048576个时 就是在消费未来时间的id 如果为某表一秒生成了10485760个id 便消耗了未来9秒的id 此9秒内服务重启并生成的id 是会重复的<br>
 * 服务器时钟回拨后 若此服务重启 id会重复<br>
 */
public class Id {

    private static Map<String, Status> statusPool = new HashMap<>(); // 各数据库表对应的生成器状态池

    /**
     * 获取下一个id id消耗不快 这个方法就够用了
     * @author Vec @ 2021-7-23 15:03:33
     */
    public static Long next() {
        return generate("");
    }

    /**
     * 根据数据库表名获取下一个id
     * @author Vec @ 2021-7-23 15:07:42
     */
    public static Long next(String table) {
        return generate(table);
    }

    /**
     * 根据时间生成一个id 主要用于数据库数据筛选
     * @author Vec @ 2022年3月7日 下午4:03:57
     */
    public static Long toId(LocalDateTime dateTime) {
        return LocalDateTimeUtil.toEpochMilli(dateTime) / 1000 << 20;
    }

    /**
     * 获取id中的时间
     * @author Vec @ 2021年7月15日13:29:11
     */
    public static LocalDateTime toLocalDateTime(long id) {
        return LocalDateTimeUtil.of((id >> 20) * 1000); // 流水号未超过1048575 会被舍弃
    }

    /**
     * 生成一个id
     * @param table 数据库表名
     * @author Vec @ 2021年7月15日13:29:18
     */
    private static synchronized Long generate(String table) {
        Status status = statusPool.get(table);
        if (status == null) {
            statusPool.put(table, new Status(0, 0));
            status = statusPool.get(table);
        }
        long now = System.currentTimeMillis() / 1000L;
        if (now > status.ts) {
            status.ts = now; // 赋最新值
            status.count = 0; // 赋最新值
        } else {
            if (status.count == 1 << 20) {
                // 当前时间戳小于等于上次使用的时间戳 并且流水号是最大值
                status.ts++; // 时间戳+1
                status.count = 0; // 归零
            } else {
                // 当前时间戳小于等于上次使用的时间戳 并且流水号未到最大值
                status.count++;
            }
        }

        return (status.ts << 20) + status.count; // '+' 比 '<<' 优先级高
    }

    private static class Status {

        private long ts;

        private int count;

        private Status(long ts, int count) {
            this.ts = ts;
            this.count = count;
        }

    }

    private Id() {
        super();
    }
    
}